/** @type {import('eslint').Linter.Config} */
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import babelParser from '@babel/eslint-parser';
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import vue from 'eslint-plugin-vue';
import globals from 'globals';
import vueEslintParser from 'vue-eslint-parser';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
	baseDirectory: __dirname,
	recommendedConfig: js.configs.recommended,
	allConfig: js.configs.all,
});

const myDataPlugin = {
	rules: {
		'enforce-data-camelcase': {
			meta: {
				type: 'problem',
				docs: { description: 'Enforce camel case in data property names.' },
				schema: [],
			},
			create(context) {
				return {
					'ObjectExpression > Property[key.name="data"] > FunctionExpression > BlockStatement > ReturnStatement > ObjectExpression > Property[key.name=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'data property "{{ name }}" should be in camelCase.',
							data: { name: node.key.name },
						});
					},
					'MemberExpression[object.object.type="ThisExpression"][object.property.name="data"] > Identifier[name=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'data property "{{ name }}" should be in camelCase.',
							data: { name: node.name },
						});
					},
					'CallExpression[callee.object.type="ThisExpression"][callee.property.name="setData"] > ObjectExpression > Property[key.name=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'data property "{{ name }}" should be in camelCase.',
							data: { name: node.key.name },
						});
					},
				};
			},
		},
		'only-console-in-uenvdev': {
			meta: {
				type: 'problem',
				docs: { description: 'Only allow console be used in uEnvDev.' },
				schema: [],
			},
			create(context) {
				const matchRule =
					':not(' +
					'IfStatement[test.left.object.object.name="process"][test.left.object.property.name="env"][test.left.property.name="NODE_ENV"]' +
					'[test.operator="==="]' +
					'[test.right.value="development"]' +
					' ' +
					'ExpressionStatement' +
					') > CallExpression[callee.object.name="console"]';
				return {
					[matchRule](node) {
						context.report({
							node,
							message: 'Not allow to use console outside uEnvDev.',
						});
					},
				};
			},
		},
		'enforce-storage-camelcase': {
			meta: {
				type: 'problem',
				docs: { description: 'Enforce camel case in storage names.' },
				schema: [],
			},
			create(context) {
				return {
					'CallExpression[callee.object.name="uni"][callee.property.name="setStorage"] > ObjectExpression > Property[key.name="key"][value.value=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'Storage key "{{ name }}" should be in camelCase.',
							data: { name: node.value.value },
						});
					},
					'CallExpression[callee.object.name="uni"][callee.property.name="getStorage"] > ObjectExpression > Property[key.name="key"][value.value=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'Storage key "{{ name }}" should be in camelCase.',
							data: { name: node.value.value },
						});
					},
					'CallExpression[callee.object.name="uni"][callee.property.name="setStorageSync"] > Literal:first-child[value=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'Storage key "{{ name }}" should be in camelCase.',
							data: { name: node.value },
						});
					},
					'CallExpression[callee.object.name="uni"][callee.property.name="getStorageSync"] > Literal[value=/.*[_-].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'Storage key "{{ name }}" should be in camelCase.',
							data: { name: node.value },
						});
					},
				};
			},
		},
		'disallow-assign-this': {
			meta: {
				type: 'problem',
				docs: { description: 'Not allow to assign `this`.' },
				schema: [],
			},
			create(context) {
				return {
					'VariableDeclarator[init.type="ThisExpression"]'(node) {
						context.report({
							node,
							message: 'Not allow to assign `this`.',
						});
					},
					'AssignmentExpression[right.type="ThisExpression"]'(node) {
						context.report({
							node,
							message: 'Not allow to assign `this`.',
						});
					},
				};
			},
		},
		'disallow-use-wx': {
			meta: {
				type: 'problem',
				docs: { description: 'Not allow to use `wx`.' },
				schema: [],
			},
			create(context) {
				const matchRule =
					':not(IfStatement[test.left.operator="typeof"][test.left.argument.name="wx"][test.operator="!=="][test.right.value="undefined"]' +
					' ' +
					'> BlockStatement CallExpression[optional=true] MemberExpression' +
					')' +
					':not(ConditionalExpression[test.left.operator="typeof"][test.left.argument.name="wx"][test.operator="!=="][test.right.value="undefined"]' +
					' ' +
					'CallExpression[optional=true] MemberExpression' +
					')' +
					':not(UnaryExpression[operator="typeof"])' +
					' > Identifier[name="wx"]';
				return {
					[matchRule](node) {
						context.report({
							node,
							message: 'Not allow to use `wx`.',
						});
					},
				};
			},
		},
		'enforce-cloudfunction-underscore': {
			meta: {
				type: 'problem',
				docs: { description: 'Enforce underscore in cloudfunction data property names.' },
				schema: [{ enum: ['server'] }],
			},
			create(context) {
				return {
					'MemberExpression[object.name="event"] > Identifier[name=/.*[A-Z].*/]'(node) {
						if (context.options[0] === 'server') {
							context.report({
								node,
								message: 'event property "{{ name }}" should be in under_score.',
								data: { name: node.name },
							});
						}
					},
					'CallExpression[callee.object.name="uniCloud"][callee.property.name="callFunction"] > ObjectExpression > Property[key.name="data"] > ObjectExpression > Property[key.name=/.*[A-Z].*/]'(
						node,
					) {
						context.report({
							node,
							message: 'CallFunction data property "{{ name }}" should be in under_score.',
							data: { name: node.key.name },
						});
					},
				};
			},
		},
		'enforce-single-property-same-line': {
			meta: {
				type: 'layout',
				docs: { description: 'Enforce placing object properties on single line' },
				fixable: 'whitespace',
			},
			create(context) {
				const sourceCode = context.sourceCode;
				return {
					'ObjectExpression[properties.length=1]'(node) {
						const firstToken = sourceCode.getFirstToken(node.properties[0]);
						const lastToken = sourceCode.getLastToken(node.properties[0]);
						const firstLine = sourceCode.getTokenBefore(firstToken).loc.start.line;
						let lastBrace = sourceCode.getTokenAfter(lastToken);
						if (sourceCode.text.slice(...lastBrace.range) === ',') {
							lastBrace = sourceCode.getTokenAfter(lastBrace);
						}
						const lastLine = lastBrace.loc.end.line;
						if (firstLine === lastLine) {
							return;
						}
						if (lastLine - firstLine > 2) {
							return;
						}
						if (
							['FunctionExpression', 'ObjectExpression', 'ArrayExpression'].includes(node.properties[0].value?.type)
						) {
							return;
						}
						context.report({
							node,
							loc: firstToken.loc,
							message: 'Single property must in the same line',
							fix(fixer) {
								const comma = sourceCode.getTokenBefore(firstToken);
								const rangeAfterComma = [comma.range[1], firstToken.range[0]];
								// Don't perform a fix if there are any comments between the comma and the next property.
								if (sourceCode.text.slice(...rangeAfterComma).trim()) {
									return null;
								}
								return fixer.replaceTextRange(rangeAfterComma, ' ');
							},
						});
						context.report({
							node,
							loc: lastToken.loc,
							message: 'Single property must in the same line',
							fix(fixer) {
								let hasComma = false;
								let comma = sourceCode.getTokenAfter(lastToken);
								if (sourceCode.text.slice(...comma.range) === ',') {
									comma = sourceCode.getTokenAfter(comma);
									hasComma = true;
								}
								const rangeAfterComma = [lastToken.range[1], comma.range[0]];
								// Don't perform a fix if there are any comments between the comma and the next property.
								if (
									sourceCode.text
										.slice((hasComma ? sourceCode.getTokenAfter(lastToken) : lastToken).range[1], comma.range[0])
										.trim()
								) {
									return null;
								}
								return fixer.replaceTextRange(rangeAfterComma, ' ');
							},
						});
					},
				};
			},
		},
	},
};

export default [
	{ ignores: ['node_modules/', 'uni_modules/', 'unpackage/', 'components/'] },
	...compat.extends('eslint:recommended', 'plugin:vue/recommended'),
	...vue.configs['flat/vue2-recommended'],
	{
		files: ['**/*.vue', '**/*.js', '**/*.mjs'],

		languageOptions: {
			parser: vueEslintParser,
			parserOptions: {
				parser: babelParser, // 用于解析 <script> 部分的解析器
				ecmaVersion: 2020, // 使用 ECMAScript 2020 语法
				sourceType: 'module', // 使用 ES6 模块
				requireConfigFile: false,
				babelOptions: {
					presets: ['@babel/preset-env'], // 默认使用 Babel 的 env 预设
				},
			},
			globals: {
				...globals.commonjs,
				...globals.node,
				uni: 'readonly',
				uniCloud: 'readonly',
				getApp: 'readonly',
				getCurrentPages: 'readonly',
				wx: 'readonly', // eslint-disable-line my-data-plugin/disallow-use-wx
				Page: 'writable',
			},
		},
		plugins: {
			vue: vue,
			'simple-import-sort': simpleImportSort,
			'my-data-plugin': myDataPlugin,
		},
		rules: {
			...vue.configs.recommended.rules,
			quotes: ['error', 'single'],
			'vue/no-unused-vars': 'warn',
			'vue/attribute-hyphenation': 'warn',
			'vue/multi-word-component-names': 'off',
			indent: ['warn', 'tab', { SwitchCase: 1 }],
			'vue/singleline-html-element-content-newline': 'off',
			'vue/html-self-closing': [
				'warn',
				{
					html: { void: 'always' },
				},
			],
			'vue/html-indent': ['warn', 'tab'],
			'vue/no-use-v-if-with-v-for': 'off',
			'vue/attributes-order': [
				'error',
				{
					order: [
						'DEFINITION',
						'LIST_RENDERING',
						'CONDITIONALS',
						'RENDER_MODIFIERS',
						'GLOBAL',
						['UNIQUE', 'SLOT'],
						'TWO_WAY_BINDING',
						'OTHER_DIRECTIVES',
						'OTHER_ATTR',
						'EVENTS',
						'CONTENT',
					],
					alphabetical: true,
				},
			],
			'simple-import-sort/imports': 'error',
			'arrow-spacing': 'error',
			'block-spacing': 'error',
			'comma-spacing': 'error',
			'comma-dangle': ['error', 'always-multiline'],
			'comma-style': 'error',
			'brace-style': 'error',
			'dot-location': ['error', 'property'],
			'func-call-spacing': 'error',
			'function-call-argument-newline': ['error', 'consistent'],
			'key-spacing': 'error',
			'keyword-spacing': 'error',
			'linebreak-style': ['error', 'windows'],
			'new-parens': 'error',
			'no-whitespace-before-property': 'error',
			'nonblock-statement-body-position': 'error',
			'no-multiple-empty-lines': 'error',
			'no-multi-spaces': 'error',
			'no-trailing-spaces': 'error',
			semi: 'error',
			'semi-spacing': 'error',
			'semi-style': 'error',
			'no-undefined': 'error',
			'array-callback-return': 'error',
			'no-constructor-return': 'error',
			'no-duplicate-imports': 'error',
			'no-promise-executor-return': 'error',
			'no-self-compare': 'error',
			'no-template-curly-in-string': 'error',
			'no-unmodified-loop-condition': 'error',
			'no-unreachable-loop': 'error',
			'no-use-before-define': 'error',
			'no-useless-backreference': 'error',
			'require-atomic-updates': 'error',
			'block-scoped-var': 'error',
			camelcase: ['error', { properties: 'never' }],
			'consistent-this': ['error', 'that'],
			curly: ['error', 'all'],
			'default-case': 'error',
			'default-case-last': 'error',
			'dot-notation': 'error',
			eqeqeq: 'error',
			'no-eq-null': 'error',
			'no-invalid-this': 'error',
			'no-var': 'error',
			'prefer-const': 'warn',
			'my-data-plugin/enforce-data-camelcase': 'error',
			'my-data-plugin/enforce-storage-camelcase': 'error',
			'my-data-plugin/only-console-in-uenvdev': 'error',
			'my-data-plugin/disallow-assign-this': 'error',
			'my-data-plugin/disallow-use-wx': 'error',
			'my-data-plugin/enforce-cloudfunction-underscore': 'error',
			'my-data-plugin/enforce-single-property-same-line': 'warn',
		},
	},
	{
		files: ['uniCloud*/**/*.js', 'vue.config.js'],
		rules: {
			'my-data-plugin/only-console-in-uenvdev': 'off',
			'my-data-plugin/enforce-cloudfunction-underscore': ['error', 'server'],
		},
	},
	{
		files: ['main.js'],
		rules: { 'no-duplicate-imports': 'off' },
	},
	{
		files: ['components/**/*.vue'],
		rules: { 'vue/multi-word-component-names': 'error' },
	},
	{
		files: ['plugins/**/*'],
		rules: { 'my-data-plugin/disallow-assign-this': 'off' },
	},
];
